//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>
#include <buola/app/ceventloop.h>
#include <buola/app/ccmdline.h>
#include <buola/app/cinitfunction.h>
#include <buola/app/cfinishfunction.h>
#include <buola/app/capp.h>
#include <buola/io.h>

#include <buola/threads/cthreadmanager.h>

#include <csignal>
#include <climits>
#include <cstdlib>
#include <clocale>
#include <unistd.h>
#include <string>
#include <atomic>

#include <sys/stat.h>

#ifdef BUOLA_DEBUG_MEMORY
#include <mcheck.h>
#endif

#if BUOLA_PLATFORM_MAC
#include <mach-o/dyld.h>
#endif //BUOLA_PLAFORM_MAC

namespace buola {

static bool sBuolaInitialized=false;
static CApp *sAppObject=nullptr;
static std::string sAppName;
static io::CURI sExecutablePath;
static int sReturnCode=0;
static bool sSignalled=false;
static int sLastSignal;
static CCmdLineFlag sOptionHelp("help",L"displays this message");
static bool sPendingSigchld=false;
static io::EPermission sUmask=(io::EPermission)022;

static void buola_fatal_signal_handler(int pSignal)
{
    fatal_error(sys_siglist[pSignal]);
}

static void buola_signal_handler(int pSignal)
{
    msg_error() << "Signal " << pSignal << " (" << sys_siglist[pSignal] << ") received... exiting\n";
    sSignalled=true;
    sLastSignal=pSignal;
    buola_exit(pSignal);
}

static void buola_sigusr1_handler(int)
{
    msg_log() << "backtracing after receiving SIGUSR1\n";
    back_trace();
}

static void buola_sigint_handler(int /*pSignal*/)
{
    static int lCtrlC=0;

    lCtrlC++;

    if(lCtrlC==1)
    {
        msg_log() << "Ctrl+C received... stopping main event loop\n";
        buola_exit(1);
        return;
    }
    else
    {
        msg_log() << "Ctrl+C received twice... forcing exit\n";
        back_trace();
        exit(1);
        return;
    }
}

static void buola_sigchld_handler(int /*pSignal*/)
{
    sPendingSigchld=true;
}

/*
static void buola_unhandled_handler()
{
    msg_log() << "Unhandled exception\n";
    back_trace();
    abort();
}
*/

static void buola_unexpected_handler()
{
    msg_log() << "Unexpected exception\n";
    back_trace();
    abort();
}

bool buola_is_init()
{
    return sBuolaInitialized;
}

namespace detail {
    void buola_init_start()
    {
        if(sBuolaInitialized)
        {
            throw XAlready("trying to initialize buola more than once");
        }

#ifdef BUOLA_DEBUG_MEMORY
        mtrace();
#endif

        abs_timer(true); //initializes the absolute time counter
        //save the initial umask and use it as the default argument for functions that create files
        sUmask=(io::EPermission)umask(0);

        //handle all relevant signals
        std::signal(SIGCHLD,buola_sigchld_handler);
        std::signal(SIGINT,buola_sigint_handler);
        std::signal(SIGUSR1,buola_sigusr1_handler);

        std::signal(SIGSEGV,buola_fatal_signal_handler);
        std::signal(SIGFPE,buola_fatal_signal_handler);

        std::signal(SIGTERM,buola_signal_handler);
        std::signal(SIGHUP,buola_signal_handler);
        std::signal(SIGQUIT,buola_signal_handler);
        std::signal(SIGPIPE,buola_signal_handler);

        //handle uncatched and unexpected exceptions
        std::set_unexpected(buola_unexpected_handler);
//        std::set_terminate(buola_unhandled_handler);
    }
    
    void buola_init_args(int pArgC,char **pArgs)
    {
        try
        {
            cmd_line().Parse(pArgC,pArgs);

            if(cmd_line().IsSet(sOptionHelp))
            {
                CCmdLine::DisplayHelp();
                exit(0);
            }

            if(!CInitFunction::RunAll())
                exit(5);
        }
        catch(XOptionParsing &pE)
        {
            msg_log() << L"\e[1;31m" << pE.what() << "\e[0;39m\n\n";

            CCmdLine::DisplayHelp();
            exit(2);
        }
        catch(std::exception &pE)
        {
            msg_log() << "\n\nCaught exception during initialization\n";
            msg_log() << pE.what() << "\n\n";
            exit(3);
        }
        catch(...)
        {
            msg_log() << "\n\nCaught unknown exception during initialization\n";
            exit(4);
        }

#if BUOLA_PLATFORM_MAC
        char lFull[PATH_MAX];
        uint32_t lSize=PATH_MAX;
        if(_NSGetExecutablePath(lFull,&lSize)!=0)
            throw XInternal(I18N("can't obtain full program name"));
        sExecutablePath=io::file(lFull);
#else //BUOLA_PLATFORM_MAC
        char lExec[PATH_MAX];
        int lLen=readlink("/proc/self/exe",lExec,PATH_MAX);
        if(lLen<0)
            throw XInternal(I18N("can't obtain full program name"));
        sExecutablePath=io::file(std::string(lExec,lLen));
#endif //BUOLA_PLATFORM_MAC
    }

    void buola_init_app_name(const std::string &pName)
    {
        if(pName.empty())
            sAppName=sExecutablePath.Name();
        else
            sAppName=pName;
    }

    void buola_init_app_object(CApp *pApp)
    {
        sAppObject=pApp;
        buola_init_app_name(pApp->GetApplicationName());
    }

    void buola_init_end()
    {
        sBuolaInitialized=true;
    }
}

void buola_init(int pArgC,char **pArgs,const std::string &pAppName)
{
    detail::buola_init_start();
    detail::buola_init_args(pArgC,pArgs);
    detail::buola_init_app_name(pAppName);
    detail::buola_init_end();
}

void buola_exit(int pReturnCode)
{
    sReturnCode=pReturnCode;
    get_main_event_loop().End();
}

int get_pid()
{
    return getpid();
}

const io::CURI &get_executable_path()
{
    assert(sBuolaInitialized);
    return sExecutablePath;
}

const std::string &get_application_name()
{
    assert(sBuolaInitialized);
    return sAppName;
}

CApp *get_application_object()
{
    assert(sBuolaInitialized);
    return sAppObject;
}

const std::string &get_host_name()
{
    static std::string lHostName;

    if(lHostName.empty())
    {
        char lName[512];
        
        gethostname(lName,512);

        lHostName=lName;
    }

    return lHostName;
}

int buola_finish()
{
    try
    {
        if(!sSignalled)
        {
            get_main_thread_manager().JoinAll();
        }
    }
    catch(std::exception &pE)
    {
        sReturnCode=4;
        msg_log() << "\n\nCaught exception while waiting for threads\n";
        msg_log() << pE.what() << "\n\n";
    }
    catch(...)
    {
        sReturnCode=5;
        msg_log() << "\n\nCaught unknown exception while waiting for threads\n";
    }

    try
    {
        CFinishFunction::RunAll();
    }
    catch(std::exception &pE)
    {
        sReturnCode=1;
        msg_log() << "\n\nCaught exception during termination\n";
        msg_log() << pE.what() << "\n\n";
    }
    catch(...)
    {
        sReturnCode=1;
        msg_log() << "\n\nCaught unknown exception during termination\n";
    }

    return sReturnCode;
}

void buola_main_loop()
{
    try
    {
        get_main_event_loop().Loop();
    }
    catch(std::exception &pE)
    {
        msg_error() << "\n\nCaught exception in main loop\n";
        msg_log() << pE.what() << "\n\n";
        buola_exit(1);
    }
    catch(...)
    {
        msg_error() << "\n\nCaught unknown exception in main loop\n";
        buola_exit(1);
    }
}

bool buola_pending_sigchld()
{
    if(sPendingSigchld)
    {
        sPendingSigchld=false;
        return true;
    }
    
    return false;
}

uint64_t get_new_uid()
{
    static std::atomic<uint64_t> lUID(1);

    return lUID++;
}

namespace io {

io::EPermission get_umask()
{
    return sUmask;
}

/*namespace io*/ }

/*namespace buola*/ }
